using System;
using System.Security;
using System.Text;
using System.Threading;

namespace InfiniTec.Security
{
    /// <summary>
    /// Extends the <see cref="ImpersonationScope"/> class by querying the user for a 
    /// username / password combination on the command line.
    /// </summary>
    public class CmdLinePromptForCredential: IDisposable
    {
        private string _TargetName;

        /// <summary>
        /// Gets or sets the target name for this request.
        /// </summary>
        public string TargetName
        {
            get { return _TargetName; }
            set { _TargetName = value; }
        }
        private string _Username;

        /// <summary>
        /// Gets or sets the username used for this request. If set prior calling <see cref="Prompt"/>,
        /// the user is only prompted for a password.
        /// After a successful call to <see cref="Prompt"/>, this property contains the
        /// username specified by the user.
        /// </summary>
        public string Username
        {
            get { return _Username; }
            set { _Username = value; }
        }
        private SecureString _Password;

        /// <summary>
        /// Returns the password that was provided by the user.
        /// </summary>
        public SecureString Password
        {
            get { return _Password; }
        }

        private bool _SaveCredential;

        /// <summary>
        /// After a successful call to <see cref="Prompt"/>, this property returns true if the 
        /// user chose "Save password". False, otherwise.
        /// </summary>
        public bool SaveCredential
        {
            get { return _SaveCredential; }
            set { _SaveCredential = value; }
        }

        private QueryCredentialOptions _Options;

        /// <summary>
        /// Gets or sets the options used when querying the user for a username/password combination.
        /// </summary>
        public QueryCredentialOptions Options
        {
            get { return _Options; }
            set { _Options = value; }
        }

        /// <summary>
        /// Creates a new instance of this class.
        /// </summary>
        /// <param name="targetName">The target name for this request.</param>
        public CmdLinePromptForCredential(string targetName) : this(targetName, null) { }
        /// <summary>
        /// Creates a new instance of this class, using the specified username.
        /// </summary>
        /// <param name="targetName">The target name for this request.</param>
        /// <param name="username">The username for this request.</param>
        public CmdLinePromptForCredential(string targetName, string username)
            :
            this(targetName, username, QueryCredentialOptions.CompleteUsername |
            QueryCredentialOptions.DoNotPersist) { }
        /// <summary>
        /// Creates a new instance of this class, using the specified username.
        /// </summary>
        /// <param name="targetName">The target name for this request.</param>
        /// <param name="options">The options for this request.</param>
        public CmdLinePromptForCredential(string targetName, QueryCredentialOptions options)
            :
            this(targetName, null, options) { }
        /// <summary>
        /// Creates a new instance of this class, using the specified username.
        /// </summary>
        /// <param name="targetName">The target name for this request.</param>
        /// <param name="options">The options for this request.</param>
        /// <param name="username">The username for this request.</param>
        public CmdLinePromptForCredential(string targetName, string username, QueryCredentialOptions options)
        {
            _TargetName = targetName;
            _Options = options;
            _Username = username;
        }

        /// <summary>
        /// Prompts the user for a username and/or password.
        /// </summary>
        /// <returns>True, if a username/password combination has been entered. False, otherwise.</returns>
        public bool Prompt()
        {
            StringBuilder username;
            char[] password;

            password = new char[256 + 1];
            unsafe
            {
                fixed (char* buffer = password)
                {
                    Array.Clear(password, 0, password.Length);

                    try
                    {
                        username = new StringBuilder(256+1);
                        if (!string.IsNullOrEmpty(Username)) username.Append(Username);

                        bool savePassword = _SaveCredential;

                        QueryCredentialError result = UnsafeNativeMethods.CredUICmdLinePromptForCredentials(TargetName, IntPtr.Zero,
                                                                                                            0, username, username.Capacity, buffer, 256+1, ref savePassword, Options);

                        if (result == QueryCredentialError.Cancelled) return false;

                        if (result != QueryCredentialError.None)
                            throw new QueryCredentialDialogException(result, "An exception occured querying the user credentials.");

                        _SaveCredential = savePassword;
                        _Username = username.ToString();

                        int length = 0;
                        while (length < password.Length && password[length] != 0) length++;

                        _Password = new SecureString(buffer, length);
                    }
                    finally
                    {
                        Array.Clear(password, 0, password.Length);
                    }
                }
            }
       
            return true;
        }

        #region IDisposable Members

        /// <summary>
        /// Releases all managed and unmanaged resources held by this instance
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Releases all managed and unmanaged resources held by this instance
        /// </summary>
        /// <param name="disposing">True, if called from the dispose method. False, otherwise</param>
        protected virtual void Dispose(bool disposing)
        {
            SecureString str;

            if (disposing)
            {
                str = Interlocked.Exchange(ref _Password, null);
                if (str != null) str.Dispose();
            }
        }

        #endregion
    }
}
